#include<linux/fs.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/miscdevice.h>
#include<linux/slab.h>
#include<linux/gfp.h>
#include<linux/uaccess.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xieziao");
MODULE_DESCRIPTION("ioctl_interface");

//先定义一些cmd码，防止和其他设备冲突，CMD选一个没用过的magic number
#define CMD 0x24
#define CMD0		_IO(CMD,0x00)
#define CMD1		_IO(CMD,0x01)
#define CMD2		_IO(CMD,0x02)
#define CMD3		_IO(CMD,0x03)

static long ioctl_interface_ioctl(struct file *filp, unsigned int ioctl, 
                                  unsigned long arg)
{
	char *buf;
	void *tmp; //展示一下一个函数申请多个内存的常见写法
	long ret;
	switch(ioctl)
	{
		case CMD0:
			buf = memdup_user( (void __user *)arg, 10); //类似copy_from_user
			if (IS_ERR(buf)){ //判定一下返回的是不是合法值
                ret = PTR_ERR(buf); //记录一下错误码
				buf = NULL;
				goto free_buf; //一般goto到指定语句直接跳过剩下的过程并释放所有资源，以免资源分配混乱
        	}

			//kmalloc使用slab分配器，__get_free_page使用buddy分配页
			tmp = (void *)__get_free_page(GFP_KERNEL);
			if (!tmp)
			{
				ret = -ENOMEM;
				goto free_tmp;
			}
			printk(KERN_WARNING "interface 0, %s",buf);
			break;
		case CMD1:
			pr_info("interface 1\n");
			break;
		case CMD2:
			pr_info("interface 2\n");
			break;
		case CMD3:
			pr_info("interface 3\n");
			break;
		default:
			pr_info("Invalid argument\n");
			return -EINVAL;
	}
	
	//一定要注意申请内存的顺序，以释放所有内存
	free_page((unsigned long)tmp); //强转一下，内核代码尽量不要Warning，否则不优雅
free_tmp:
	kfree(buf);
free_buf:
	return ret;
}

static struct file_operations ioctl_interface_ops = {
	.unlocked_ioctl = ioctl_interface_ioctl,
	.owner		= THIS_MODULE,
};

static struct miscdevice ioctl_interface_dev = {
	MISC_DYNAMIC_MINOR,
	"ioctl_interface",
	&ioctl_interface_ops,
};

static int __init ioctl_interface_init(void)
{
	int ret;
	ret = misc_register(&ioctl_interface_dev);
	return ret;
}

static void __exit ioctl_interface_exit(void)
{
	misc_deregister(&ioctl_interface_dev);
}

module_init(ioctl_interface_init);
module_exit(ioctl_interface_exit);
